package org.capybara.coffeehouse.insteon;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.log4j.Logger;
import org.capybara.coffeehouse.device.InsteonDevice;

public class PlmDriver implements Runnable {
	private final RS232SerialPort port;
	private final static Logger log = Logger.getLogger(PlmDriver.class);
	
	private final BlockingQueue<InsteonMessage> outputQueue = new LinkedBlockingQueue<InsteonMessage>();

	
	private final List<InsteonDevice> devices = new ArrayList<>();
	private AtomicBoolean running = new AtomicBoolean(false);
	
	public PlmDriver(RS232SerialPort port) {
		
		this.port = port;
	}
	
	public void addEventListner(InsteonDevice d) {
		if (running.get() == true) {
			throw new IllegalStateException("Can't add listener while running");
		}
		devices.add(d);
	}
	
	public void sendInsteonMessage(InsteonMessage m) {
		outputQueue.add(m);
	}
	
	public void run() {
		running.set(true);
		while (true) {
			try	{
				
				// send any messages in the output queue
				List<InsteonMessage> outputMessages = new ArrayList<>();
				outputQueue.drainTo(outputMessages);
				for (InsteonMessage m : outputMessages) {
					txMessage(m);
				}
				
				PlmMessage message = rxMessage(); // times out after 500ms
				if (message == null) {
					log.trace("rx timeout");
					// if there is stuff in the queue to send, send it
					
				} else {
					if (message.getCode() == PlmMessage.PlmMessageCode.STANDARD_MESSAGE_RX || message.getCode() == PlmMessage.PlmMessageCode.EXTENDED_MESSAGE_RX) {
						InsteonMessage im = message.getInsteonMessage();
						log.info("PLM rx: "+im);
						// process the message and send the info to registered drivers
						for (InsteonDevice d : devices) {
							if (im.getFromAddress().equals(d.getAddress())) {
								log.trace("   forwarding to: "+d);
								d.handleMessage(im);
							}
						}
					} else {
						log.debug("PLM rx: "+message);
					}
				}
			} catch (IOException e) {
				log.error("got exception receiving message",e);
			}
//			try {
//				InsteonMessage om = outputQueue.poll();
//				if (om != null) {
//					log.info("message to Tx: "+om);
//					this.txMessage(om.getMessage());
//					Thread.sleep(1000);
//				}
//			} catch (IOException e) {
//				log.error("got exception sending message",e);
//			} catch (InterruptedException e) {
//				log.error("interrupted sending message",e);
//			}
		}
	}

	private PlmMessage rxMessage() throws IOException {
		port.setRxTimeout(500);
		byte[] rxBuf = new byte[1];
		List<Integer> messageData = new ArrayList<Integer>();
		int messagePos = 0; 
		int bytesLeft = 0;
		PlmMessage message = null;
		
		while (true) {
			if (port.receive(rxBuf, 1) > 0) {
				if (messagePos == 0) {
					// first byte, should be 0x02
					if (rxBuf[0] != 0x02) {
//						log.error("expecting start of message (0x02) but got "+Util.getHexByte(rxBuf[0]));
						return null;
					}
				}
				else if (messagePos == 1) {
					try {
						if (rxBuf[0] == 0x62) {
							//log.info("plm message echo, ignoring");
							return null;
						} 
						message = new PlmMessage(rxBuf[0]);
					} catch (IllegalArgumentException e) {
						log.error("got invalid message code byte: "+Util.getHexByte(rxBuf[0]));
						return null;
					}					
					bytesLeft = message.getLength();
					log.trace("got the code byte. bytesLeft: "+bytesLeft);
				}
				else if (messagePos > 1) {
					log.trace("got a message byte. bytesLeft: "+bytesLeft);
					if (bytesLeft > 0) {
						messageData.add(rxBuf[0] & 0xFF);
						bytesLeft--;
					} 
					if (bytesLeft == 0) {
						log.trace("got the whole message!");
						message.setData(messageData);
						return message;
					}
				}
				messagePos++;
			} else {
				// rx timeout, receive failed
				return null;
			}
		}
	}
	
	private void txMessage(InsteonMessage message) throws IOException {
		byte [] header = new byte[] {0x02, 0x62};
		log.debug("sending message: "+Util.getHexString(header)+message.getPlmMessage());	
		port.send(header, header.length);
		port.send(message.getPlmMessage(), message.getPlmMessage().length);
	}

	private void txMessage(byte[] message) throws IOException, InterruptedException {
		byte [] header = new byte[] {0x02};
		log.debug("sending plm message 0x"+Util.getHexString(message));
		port.send(header, header.length);
		port.send(message, message.length);
	}
}
